Today’s schedule

Packages

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(terra)
## Warning: package 'terra' was built under R version 4.4.1
## terra 1.8.10
## 
## Attaching package: 'terra'
## 
## The following object is masked from 'package:tidyr':
## 
##     extract
library(sf)
## Warning: package 'sf' was built under R version 4.4.1
## Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE
library(leaflet) #new
library(leaflet.extras) #new
## Warning: package 'leaflet.extras' was built under R version 4.4.1

Leaflet

Leaflet is….

…a Javascript library with an API we can access in R.

Wait, that’s a LOT of jargon. Can we add some clarity?

Features

  • Interactive panning/zooming
  • Compose maps using arbitrary combinations of:
    • Map tiles
    • Markers
    • Polygons
    • Lines
    • Popups
    • GeoJSON

Functionality

  • Create maps right from the R console or RStudio
  • Embed maps in knitr/R Markdown documents and Shiny apps
  • Easily render spatial objects from the sp or sf packages, or data frames with latitude/longitude columns
  • Use map bounds and mouse events to drive Shiny logic
  • Display maps in non spherical mercator projections
  • Augment map features using chosen plugins from leaflet plugins repository

Demonstration

A simple setup

m <- leaflet()

m

What do you get?

Let’s try again

m <- leaflet() %>%
  addTiles()

m

And now? What do you see?

Let’s build on that

m <- leaflet() %>%
  addTiles() %>%  # Add default OpenStreetMap map tiles
  addMarkers(lng = -81.350903, lat = 41.150377, popup="Our McGilvrey Hall GIS Lab")

m

What about now?

Let’s add some complexity

Create some data to plot. Let’s break this down a bit. What am I trying to do?

# First let's define a color palette we can sample from - this is only really necessary for the demo
mypal <- RColorBrewer::brewer.pal(12, "Set3")

# start with a data frame
df <- data.frame(
  lat = rnorm(100) * 2 + 41,
  lng = rnorm(100) * 2 - 81.5,
  size = runif(100, 5, 20),
  color = sample(mypal, 100, replace = T)
)

# then add the data frame to a leaflet map
m2 <- leaflet(df) %>% addTiles()

m2

What do you get? Why?

How can we interrogate the properties/attributes of an object?

The $ operator

m2$x
## $options
## $options$crs
## $crsClass
## [1] "L.CRS.EPSG3857"
## 
## $code
## NULL
## 
## $proj4def
## NULL
## 
## $projectedBounds
## NULL
## 
## $options
## named list()
## 
## attr(,"class")
## [1] "leaflet_crs"
## 
## 
## $calls
## $calls[[1]]
## $calls[[1]]$method
## [1] "addTiles"
## 
## $calls[[1]]$args
## $calls[[1]]$args[[1]]
## [1] "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
## 
## $calls[[1]]$args[[2]]
## NULL
## 
## $calls[[1]]$args[[3]]
## NULL
## 
## $calls[[1]]$args[[4]]
## $calls[[1]]$args[[4]]$minZoom
## [1] 0
## 
## $calls[[1]]$args[[4]]$maxZoom
## [1] 18
## 
## $calls[[1]]$args[[4]]$tileSize
## [1] 256
## 
## $calls[[1]]$args[[4]]$subdomains
## [1] "abc"
## 
## $calls[[1]]$args[[4]]$errorTileUrl
## [1] ""
## 
## $calls[[1]]$args[[4]]$tms
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$noWrap
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$zoomOffset
## [1] 0
## 
## $calls[[1]]$args[[4]]$zoomReverse
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$opacity
## [1] 1
## 
## $calls[[1]]$args[[4]]$zIndex
## [1] 1
## 
## $calls[[1]]$args[[4]]$detectRetina
## [1] FALSE
## 
## $calls[[1]]$args[[4]]$attribution
## [1] "&copy; <a href=\"https://openstreetmap.org/copyright/\">OpenStreetMap</a>,  <a href=\"https://opendatacommons.org/licenses/odbl/\">ODbL</a>"
## 
## 
## 
## 
## 
## attr(,"leafletData")
##          lat       lng      size   color
## 1   40.99557 -79.13563  5.589956 #FFFFB3
## 2   41.51841 -82.07183 13.634710 #8DD3C7
## 3   39.58837 -79.98233 12.287202 #8DD3C7
## 4   36.02427 -79.34099 11.800571 #8DD3C7
## 5   41.61471 -79.69419  6.796672 #FB8072
## 6   41.07081 -84.58328 14.169870 #FFFFB3
## 7   39.20428 -83.48465  6.726310 #BC80BD
## 8   39.38648 -81.08120 16.762243 #FB8072
## 9   42.91117 -84.08719 18.635823 #FDB462
## 10  42.97757 -82.08426 10.449133 #FDB462
## 11  43.61776 -79.42000 15.345889 #80B1D3
## 12  39.62743 -83.58673 19.659007 #FFED6F
## 13  37.07969 -81.27965  6.377662 #FB8072
## 14  39.62185 -81.49565  7.631777 #BC80BD
## 15  45.03158 -79.01240 11.398857 #FDB462
## 16  41.96567 -83.54479  6.826523 #D9D9D9
## 17  40.00618 -84.12575  5.875669 #8DD3C7
## 18  43.49293 -83.94984 19.223998 #FFFFB3
## 19  42.27354 -83.33615  9.413458 #FFFFB3
## 20  40.70913 -81.94301 17.432854 #BEBADA
## 21  38.90938 -82.10358  5.930295 #FFFFB3
## 22  43.05722 -80.53805  9.756234 #D9D9D9
## 23  43.54577 -78.81916 11.091415 #BC80BD
## 24  41.19503 -80.64093  6.551020 #BEBADA
## 25  38.57878 -81.84099 15.786930 #FB8072
## 26  38.58378 -80.43737 15.786754 #BC80BD
## 27  39.72807 -77.50587 18.862740 #D9D9D9
## 28  42.77109 -80.81386 19.111744 #BC80BD
## 29  44.22270 -80.13685  6.637261 #FCCDE5
## 30  39.27048 -83.17801 10.424794 #FFFFB3
## 31  40.92306 -79.88249 10.578742 #FDB462
## 32  41.11225 -81.04566  8.981571 #FFFFB3
## 33  40.21275 -80.95464 15.209068 #FFED6F
## 34  41.54495 -81.69650 11.198912 #CCEBC5
## 35  37.26881 -78.70891  5.168262 #FFED6F
## 36  39.55594 -80.39995  9.460870 #FDB462
## 37  36.68614 -83.35930 10.466847 #BEBADA
## 38  42.06948 -80.32987  7.980701 #8DD3C7
## 39  41.83519 -84.58168 15.495430 #D9D9D9
## 40  41.74426 -79.89403  7.314745 #FFFFB3
## 41  43.63476 -81.66410 19.668826 #80B1D3
## 42  42.56280 -82.41770  7.623219 #B3DE69
## 43  39.68978 -84.96293  9.950413 #B3DE69
## 44  44.94753 -77.82024 11.327422 #FFFFB3
## 45  40.65368 -78.50057 10.269610 #CCEBC5
## 46  39.22979 -82.24579 19.261176 #BEBADA
## 47  44.07065 -81.63558 13.004268 #FB8072
## 48  40.36046 -84.64513  6.912204 #FDB462
## 49  40.80705 -79.64504 12.464519 #FCCDE5
## 50  42.56957 -79.79366 17.316795 #FFFFB3
## 51  43.03161 -80.02926 19.236746 #FFFFB3
## 52  41.43904 -80.75981  5.145617 #80B1D3
## 53  40.04199 -81.15778 13.480114 #FFFFB3
## 54  43.55065 -79.91475  5.485484 #D9D9D9
## 55  41.05792 -78.86068 10.242263 #80B1D3
## 56  38.86173 -81.66307 18.627972 #BC80BD
## 57  40.47928 -81.42816 10.962013 #BC80BD
## 58  39.66669 -81.55837 19.552184 #D9D9D9
## 59  42.00286 -78.83213  9.499974 #8DD3C7
## 60  38.42290 -83.60183 17.805416 #B3DE69
## 61  46.28538 -81.50380  8.364720 #FCCDE5
## 62  44.84312 -77.60632 10.032009 #FCCDE5
## 63  38.50044 -84.72521 12.245606 #FFFFB3
## 64  39.49851 -83.15436 16.234848 #CCEBC5
## 65  41.98286 -78.79072  5.012674 #BC80BD
## 66  36.81610 -84.71510 11.772750 #B3DE69
## 67  44.08290 -82.41083  6.536815 #B3DE69
## 68  42.04413 -80.28196 11.855492 #FFED6F
## 69  39.69804 -81.37702  5.267861 #FFFFB3
## 70  39.10524 -80.87203 12.511442 #8DD3C7
## 71  40.89387 -81.02553  9.459610 #FCCDE5
## 72  43.95761 -81.64442  9.045999 #FFFFB3
## 73  42.32377 -82.98972  6.734687 #B3DE69
## 74  43.99951 -82.99287  8.990918 #FFED6F
## 75  43.69136 -81.29568 18.295786 #8DD3C7
## 76  44.55294 -84.79464 18.206944 #BEBADA
## 77  39.69705 -83.94369 14.111976 #FDB462
## 78  40.71315 -81.55757 12.101988 #D9D9D9
## 79  42.00901 -86.38394 14.269304 #B3DE69
## 80  41.66195 -80.31610  7.334835 #D9D9D9
## 81  39.57646 -79.49651  5.376891 #FFED6F
## 82  38.07819 -85.77021 14.029262 #CCEBC5
## 83  42.09102 -81.57860  7.852826 #FDB462
## 84  40.56325 -81.34357 19.253815 #FFFFB3
## 85  39.21289 -79.80518  9.615235 #FDB462
## 86  44.20685 -81.95846  6.812211 #CCEBC5
## 87  40.95139 -85.58172  7.794516 #FFED6F
## 88  39.64487 -85.90070 15.946174 #8DD3C7
## 89  37.02554 -81.26042  7.025145 #FB8072
## 90  39.64444 -81.40372 16.115854 #8DD3C7
## 91  39.80332 -76.97088  7.306852 #D9D9D9
## 92  41.44260 -81.67425 11.255314 #FFED6F
## 93  40.53179 -83.21473  7.941580 #FFED6F
## 94  40.29679 -81.22706 18.578634 #FFFFB3
## 95  41.83633 -79.28368 11.910308 #FB8072
## 96  43.00870 -77.94653  9.014591 #FCCDE5
## 97  40.54698 -83.43580  8.368629 #CCEBC5
## 98  40.77970 -80.14204 10.636852 #FFED6F
## 99  39.20020 -82.22742  6.221372 #FCCDE5
## 100 41.84033 -79.26274  8.804855 #BEBADA

What do the data look like?

Let’s try again to visualize it. Again, break down the code first

# first one
m2 %>% addCircleMarkers(radius = ~size, color = ~color, fill = FALSE)
## Assuming "lng" and "lat" are longitude and latitude, respectively
# second one
m2 %>% addCircleMarkers(radius = runif(100, 4, 10), color = c('red'))
## Assuming "lng" and "lat" are longitude and latitude, respectively

What happened this time???

Let’s add some more tiles

# Let's check out some other tiles

m <- leaflet() %>% setView(lng = -81.34, lat = 41.145, zoom = 14)
m %>% addTiles()
# third party tiles using addProvider() function

m %>% addProviderTiles(providers$Stadia.StamenToner)
m %>% addProviderTiles(providers$CartoDB.Positron)
m %>% addProviderTiles(providers$CartoDB.DarkMatter)
m %>% addProviderTiles(providers$Esri.NatGeoWorldMap)

Give it a shot. What do you like?

Let’s use some of our data

parks <- sf::read_sf("../data/static_mapping/oh_parks.gpkg") %>%
  sf::st_transform(., "EPSG:4326") # transform to WGS84 to make Leaflet happy

# set up the map, zoom out a bit
mp <- leaflet(data = parks) %>% setView(lng = -81.34, lat = 41.145, zoom = 10)
mp %>% addTiles() %>% 
  addPolygons(popup = ~NAME, label = ~NAME)

What’s the difference between popup and label?

Lines

Who wants to help break down this code?

portage_streams <- sf::read_sf("../data/static_mapping/tl_2022_39133_linearwater/tl_2022_39133_linearwater.shp") %>%
  sf::st_transform(., "EPSG:4326") # transform to WGS84 to make Leaflet happy

leaflet(data = portage_streams) %>% 
  setView(lng = -81.34, lat = 41.145, zoom = 10) %>% 
  addTiles() %>%
  addPolylines(., color = "blue", 
               popup = ~paste0(FULLNAME, ": ", LINEARID))

What happened?

Multiple layers

(Note, there’s a difference here in calling various datasets)

m.both <- leaflet() %>%
  setView(lng = -81.34, lat = 41.145, zoom = 10) %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMap) %>%
  addPolygons(data = parks, popup = ~NAME, label = ~NAME, color = "green") %>%
  addPolylines(data = portage_streams, color = "blue", 
               popup = ~paste0(FULLNAME, ": ", LINEARID))

m.both

Let’s practice

Team activity

Tasks

GROUP 1

  • Make a choropleth map of the population density of Ohio Counties. There should be no more than 7 classes
  • When the user “mouses over” a polygon, a pop-up with the county name and population density should appear
  • Place a legend in the bottom-right corner

Extra challenges:

  1. Add a minimap to the interface
  2. Add a “locate me” button to the interface

GROUP 2

  • Make a map of the monitoring stations and removed dams in the Chesapeake Bay Watershed
  • The monitoring stations should be symbolized as a filled, semi-transparent circle with the size scaled to the Drainage Area of the station
  • Dam layer should be shaded by the year it was removed
  • Add controls to turn individual layers on and off

Extra challenges:

  1. Use a custom icon for the dam layer
  2. Add a control to the interface to change the base map